package orxatas.travelme.sync;

import java.util.ArrayList;
import java.util.List;

import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;

import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
import orxatas.travelme.activity.AsyncActivity;
import orxatas.travelme.databases.TravelmeDatabase;
import orxatas.travelme.databases.exceptions.PlaceNotInLocal;
import orxatas.travelme.entity.Geopoint;
import orxatas.travelme.entity.Group;
import orxatas.travelme.entity.Place;
import orxatas.travelme.entity.PlaceType;
import orxatas.travelme.manager.AccountManager;
import orxatas.travelme.manager.PlaceManager;
import orxatas.travelme.sync.SyncOptions.ParseAnswer;
import orxatas.travelme.sync.exceptions.ImposibleFindNearPlaces;
import orxatas.travelme.sync.exceptions.ImposibleFindPlaceByName;
import orxatas.travelme.sync.exceptions.ImposibleGetThisLocationAsPlace;

public class SyncPlaces extends SyncOptions {
	
	/**
	 * True si hay una petición de actualización en curso. En caso contrario false.
	 * */
	static private boolean synchronizing = false;

	private PlaceManager placeManager;
	private AsyncActivity asyncActivity;

	/**
	 * Base de datos.
	 * */
	private SQLiteDatabase trvlmdb;
	private TravelmeDatabase databaseHelper;
	
	public SyncPlaces(PlaceManager placeManager, AsyncActivity asyncActivity) {
		this.placeManager = placeManager;
		this.asyncActivity = asyncActivity;
		
		databaseHelper = new TravelmeDatabase(asyncActivity.getActivity(), new AccountManager(asyncActivity).getUserLogged().getId());
	}
	
	private class AnswerGetPlace extends Answer<ArrayList<TroovelPlace.Detail>>{}
	private class GetPlaceCall extends AsyncInternetConnection{

		private final int idPlace;
		
		public GetPlaceCall(int idPlace) {
			super(TROOVELWS);
			this.idPlace = idPlace;
			
			List<NameValuePair> pairs = basicGETParamsTrovel(PACK_PLACE, OP_PLACE_DETAIL);
			pairs.add(new BasicNameValuePair("ids", ""+idPlace));
			
			addGETParams(pairs);
		}

		@Override
		protected void onPostExecute(String response) {
			try {
				Answer<ArrayList<TroovelPlace.Detail>> answer = new ParseAnswer<AnswerGetPlace>().parse(response, true, AnswerGetPlace.class);
				if(answer.getState() != 0 || answer.getObj().size() == 0) return;
				TroovelPlace.Detail troovelPlace = answer.getObj().get(0);
				
				savePlaceToDB(troovelPlace);
				
				placeManager.syncInProgressEnd();
				placeManager.asyncNotice(AsyncNoticeCode.PLACES_CHANGED);
			} catch (WithoutObject e) {
				Log.e("TROOVEL", "Bad answer. Bad call to obtain a place: "+idPlace);
			}
			
		}
		
	}
	
	private void savePlaceToDB(TroovelPlace.Detail tp){
		Place p = getPlaceFromDB(tp.id);
		
		ContentValues v = new ContentValues();
		v.put(TravelmeDatabase.PLACE_CIDPLACE, tp.id);
		v.put(TravelmeDatabase.PLACE_CNAME, tp.name);
		v.put(TravelmeDatabase.PLACE_CLAT, tp.lat);
		v.put(TravelmeDatabase.PLACE_CLON, tp.lon);
		v.put(TravelmeDatabase.PLACE_CDESCR, tp.desc);
		v.put(TravelmeDatabase.PLACE_CPLACETYPE, tp.place_type);
		if(tp.location_desc != null)
			v.put(TravelmeDatabase.PLACE_CHUMANLOCATION, tp.location_desc);
		if(tp.rate != -1)
			v.put(TravelmeDatabase.PLACE_CRATE, tp.rate);
		
		trvlmdb = databaseHelper.getWritableDatabase();
		if(p == null)
			trvlmdb.insert(TravelmeDatabase.PLACE_TNAME, null, v);
		else {
			trvlmdb.update(TravelmeDatabase.PLACE_TNAME, v, TravelmeDatabase.PLACE_CIDPLACE+" = "+tp.id, null);
			p.setRate(tp.rate);
			p.setLongLocation(tp.location_desc);
		}
		trvlmdb.close();
	}
	
	/**
	 * Obtain a place.
	 * 
	 * @param idPlace The ID of the place to obtain.
	 * 
	 * @return The place requested.
	 * 
	 * @throws PlaceNotInLocal When place don't in local. It execs a Async call if posible.
	 * */
	public Place getPlace(int idPlace) throws PlaceNotInLocal{
		Place requestedPlace = getPlaceFromDB(idPlace);
		if(requestedPlace == null) {
			new GetPlaceCall(idPlace).execute();
			throw new PlaceNotInLocal(idPlace);
		}
		
		return requestedPlace;
	}
	
	private Place getPlaceFromDB(int idPlace){
		trvlmdb = databaseHelper.getReadableDatabase();
		
		Cursor c = trvlmdb.rawQuery("SELECT * FROM "+TravelmeDatabase.PLACE_TNAME+" WHERE "+TravelmeDatabase.PLACE_CIDPLACE+" = "+idPlace, null);
		
		if(!c.moveToFirst()) {
			c.close();
			trvlmdb.close();
			return null;
		}
		
		int idc_idplace = c.getColumnIndex(TravelmeDatabase.PLACE_CIDPLACE);
		int idc_name = c.getColumnIndex(TravelmeDatabase.PLACE_CNAME);
		int idc_placeType = c.getColumnIndex(TravelmeDatabase.PLACE_CPLACETYPE);
		int idc_humanLocation = c.getColumnIndex(TravelmeDatabase.PLACE_CHUMANLOCATION);
		int idc_lat = c.getColumnIndex(TravelmeDatabase.PLACE_CLAT);
		int idc_lon = c.getColumnIndex(TravelmeDatabase.PLACE_CLON);
		int idc_descr = c.getColumnIndex(TravelmeDatabase.PLACE_CDESCR);
		int idc_rate = c.getColumnIndex(TravelmeDatabase.PLACE_CRATE);
		
		int placeID = c.getInt(idc_idplace);
		String name = c.getString(idc_name);
		PlaceType placeType = PlaceType.toEnum(c.getString(idc_placeType));
		String humanLoc = c.getString(idc_humanLocation);
		Geopoint coor = new Geopoint(c.getDouble(idc_lat), c.getDouble(idc_lon));
		String descr = c.getString(idc_descr);
		float rate = c.getFloat(idc_rate);
		
		Place requestedPlace = new Place(placeID, name, coor);
		requestedPlace.setPlaceType(placeType);
		requestedPlace.setLongLocation(humanLoc);
		requestedPlace.setDescription(descr);
		requestedPlace.setRate(rate);
		
		c.close();
		trvlmdb.close();
		
		return requestedPlace;
	}
	
	private class AnswerGetThisLocationCall extends Answer<ArrayList<TroovelSearch.My_location>>{}
	/**
	 * Geolocalice a coordinate as a Place. Is a synchronous call.
	 * 
	 * @para coordinate
	 * 
	 * @return List of places as a TroovelSearch.My_location object.
	 * @throws ImposibleGetThisLocationAsPlace 
	 * */
	public ArrayList<TroovelSearch.My_location> getThisLocation(Geopoint coordinate) throws ImposibleGetThisLocationAsPlace{
		SyncInternetConnection getMyLocatonCall = new SyncInternetConnection(TROOVELWS);
		List<NameValuePair> pairs = basicGETParamsTrovel(PACK_SEARCH, OP_SEARCH_MYLOCATION);
		pairs.add(new BasicNameValuePair("lat", ""+coordinate.getLatitude()));
		pairs.add(new BasicNameValuePair("lon", ""+coordinate.getLongitude()));
		
		String response = getMyLocatonCall.send();
		try {
			Answer<ArrayList<TroovelSearch.My_location>> answer = new ParseAnswer<AnswerGetThisLocationCall>().parse(response, true, AnswerGetThisLocationCall.class);
			if(answer.getState() != 0)
				throw new ImposibleGetThisLocationAsPlace(coordinate);
			
			ArrayList<TroovelSearch.My_location> mlocList = answer.getObj();
			if(mlocList.size() == 0) throw new ImposibleGetThisLocationAsPlace(coordinate);
			
			return mlocList;

		} catch (WithoutObject e) {
			throw new ImposibleGetThisLocationAsPlace(coordinate);
		}
	}

	private class AnswerFindPlace extends Answer<ArrayList<TroovelSearch.BasicPlace>>{};
	/**
	 * Find a list of place by name and optional filtering.
	 * 
	 * @param searchName The name for the search
	 * @param placeType Filtering of the type. Place type (list from CITY, POI, etc.).
	 * @param enclosingPlace Place id of the enclosing place (must be either type CITY or REGION)
	 * 
	 * @throws ImposibleFindPlaceByName 
	 * */
	public ArrayList<TroovelSearch.BasicPlace> findPlace(String searchName, PlaceType [] placeType, Integer enclosingPlace) throws ImposibleFindPlaceByName{
		SyncInternetConnection findPlaceCall = new SyncInternetConnection(TROOVELWS);
		List<NameValuePair> pairs = basicGETParamsTrovel(PACK_SEARCH, OP_SEARCH_NAME);
		
		pairs.add(new BasicNameValuePair("name", searchName));
		if(placeType != null && placeType.length > 0)
			pairs.add(new BasicNameValuePair("place_type", PlaceType.toString(placeType)));
		if(enclosingPlace != null && enclosingPlace != -1)
			pairs.add(new BasicNameValuePair("enclosing_place", ""+enclosingPlace));
		
		findPlaceCall.addGETParams(pairs);
		String response = findPlaceCall.send();
		
		try {
			Answer<ArrayList<TroovelSearch.BasicPlace>> answer = new ParseAnswer<AnswerFindPlace>().parse(response, true, AnswerFindPlace.class);
			if(answer.getState() != 0)
				throw new ImposibleFindPlaceByName(searchName);
			
			ArrayList<TroovelSearch.BasicPlace> searchedPlaces = answer.getObj();
			
			return searchedPlaces;
		} catch (WithoutObject e) {
			throw new ImposibleFindPlaceByName(searchName);
		}
	}
	
	private class AnswerSearchNear extends Answer<ArrayList<TroovelSearch.BasicPlace>>{};
	/**
	 * 
	 * @throws ImposibleFindNearPlaces 
	 * */
	public ArrayList<TroovelSearch.BasicPlace> searchNear(Geopoint coordinate, PlaceType [] placeType, Integer enclosingPlace) throws ImposibleFindNearPlaces{
		SyncInternetConnection searchNearCall = new SyncInternetConnection(TROOVELWS);
		List<NameValuePair> pairs = basicGETParamsTrovel(PACK_SEARCH, OP_SEARCH_LOCATION);

		pairs.add(new BasicNameValuePair("lat", ""+coordinate.getLatitude()));
		pairs.add(new BasicNameValuePair("lon", ""+coordinate.getLongitude()));
		if(placeType != null && placeType.length > 0)
			pairs.add(new BasicNameValuePair("place_type", PlaceType.toString(placeType)));
		if(enclosingPlace != null && enclosingPlace != -1)
			pairs.add(new BasicNameValuePair("enclosing_place", ""+enclosingPlace));
		searchNearCall.addGETParams(pairs);
		
		String response = searchNearCall.send();
		
		try {
			Answer<ArrayList<TroovelSearch.BasicPlace>> answer = new ParseAnswer<AnswerSearchNear>().parse(response, true, AnswerSearchNear.class);
			if(answer.getState() != 0)
				throw new ImposibleFindNearPlaces(coordinate);
			
			ArrayList<TroovelSearch.BasicPlace> placesFinded = answer.getObj();
			
			return placesFinded;
		} catch (WithoutObject e) {
			throw new ImposibleFindNearPlaces(coordinate);
		}
	}
}
